package com.ruoyi.dbapi.rocketapi.controller;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.dbapi.rocketapi.config.RocketApiProperties;
import com.ruoyi.dbapi.rocketapi.entity.ApiDirectory;
import com.ruoyi.dbapi.rocketapi.entity.ApiExample;
import com.ruoyi.dbapi.rocketapi.entity.ApiInfo;
import com.ruoyi.dbapi.rocketapi.entity.ApiParams;
import com.ruoyi.dbapi.rocketapi.entity.ApiResult;
import com.ruoyi.dbapi.rocketapi.entity.vo.ProvideCompletionReq;
import com.ruoyi.dbapi.rocketapi.entity.vo.RunApiReq;
import com.ruoyi.dbapi.rocketapi.entity.vo.RunApiRes;
import com.ruoyi.dbapi.rocketapi.extend.ApiInfoContent;
import com.ruoyi.dbapi.rocketapi.extend.IScriptEncrypt;
import com.ruoyi.dbapi.rocketapi.script.IScriptParse;
import com.ruoyi.dbapi.rocketapi.service.ApiInfoService;
import com.ruoyi.dbapi.rocketapi.service.CompletionService;
import com.ruoyi.dbapi.rocketapi.service.DataSourceService;
import com.ruoyi.dbapi.rocketapi.utils.RequestUtils;

import jakarta.servlet.http.HttpServletRequest;

/**
 * Api ui 数据接口
 */
@RestController
@RequestMapping("${sgic.db-api.base-register-path:/interface-ui}")
@ConditionalOnProperty(name = "sgic.db-api.view-enabled", havingValue = "true", matchIfMissing = true)
public class ApiController {

	@Autowired
	private ApiInfoContent apiInfoContent;

	@Autowired
	@Lazy
	private IScriptParse scriptParse;

	@Autowired
	private IScriptEncrypt scriptEncrypt;

	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private RocketApiProperties properties;

	@Autowired
	private DataSourceService dataSourceService;

	@Autowired
	private ApiInfoService apiInfoService;

	@Autowired
	private CompletionService completionService;

	/**
	 * 查询 API 列表
	 */
	@GetMapping("/api-list")
	public ApiResult getPathList(boolean isDb) throws Exception {
		Collection<ApiInfo> colls = apiInfoService.getPathList(true);
		List<ApiInfo> result = colls.stream()
				.sorted(Comparator.comparing(ApiInfo::getName).thenComparing(ApiInfo::getFullPath))
				.collect(Collectors.toList());

		result = result.stream().map(item -> {
			ApiInfo apiInfo = new ApiInfo();
			BeanUtils.copyProperties(item, apiInfo);
			return apiInfo;
		}).collect(Collectors.toList());

		return ApiResult.success(result);
	}

	/**
	 * 单个获取
	 */
	@GetMapping("/api-info/{id}")
	public ApiResult getPathList(@PathVariable String id) throws Exception {
		ApiInfo apiInfo = apiInfoService.getPathList(false).stream().filter(item -> item.getId().equals(id)).findFirst()
				.orElse(null);
		if (apiInfo == null || StringUtils.isEmpty(apiInfo.getScript())) {
			return ApiResult.success(apiInfo);
		}

		ApiInfo resultInfo = new ApiInfo();
		BeanUtils.copyProperties(apiInfo, resultInfo);
		resultInfo.setScript(scriptEncrypt.decrypt(resultInfo.getScript()));
		return ApiResult.success(resultInfo);
	}

	/**
	 * SAVE APIINFO
	 * 
	 * @param apiInfo
	 */
	@PostMapping("/api-info")
	public ApiResult saveOrUpdateApiInfo(@RequestBody ApiInfo apiInfo, HttpServletRequest request) {
		if (StringUtils.isEmpty(apiInfo.getDirectoryId())) {
			return ApiResult.fail("A directory is a must");
		}
		try {
			if (!StringUtils.isEmpty(apiInfo.getScript())) {
				apiInfo.setScript(scriptEncrypt.encrypt(apiInfo.getScript()));
			}

			String apiInfoId = apiInfoService.saveApiInfo(apiInfo);

			return ApiResult.success(apiInfoId);
		} catch (Exception e) {
			e.printStackTrace();
			return ApiResult.fail(e.getMessage());
		}

	}

	/**
	 * REMOVE APIINFO
	 * 
	 * @param apiInfo
	 */
	@DeleteMapping("/api-info")
	public ApiResult deleteApiInfo(@RequestBody ApiInfo apiInfo, HttpServletRequest request) {
		try {
			apiInfoService.deleteApiInfo(apiInfo);
			return ApiResult.success(null);
		} catch (Exception e) {
			e.printStackTrace();
			return ApiResult.fail(e.getMessage());
		}

	}

	/**
	 * 脚本执行
	 */
	@PostMapping("/api-info/run")
	public ApiResult runScript(@RequestBody RunApiReq runApiReq, HttpServletRequest request) {
		RunApiRes runApiRes = new RunApiRes();
		try {
			apiInfoContent.setIsDebug(runApiReq.isDebug());
			ApiInfo apiInfo = ApiInfo.builder().fullPath(runApiReq.getPattern()).options(runApiReq.getOptions())
					.datasource(runApiReq.getDatasource()).script(runApiReq.getScript()).build();
			ApiParams apiParams = ApiParams.builder().header(decodeHeaderValue(runApiReq.getHeader()))
					.pathVar(getPathVar(runApiReq.getPattern(), runApiReq.getUrl())).param(getParam(runApiReq.getUrl()))
					.body(buildBody(runApiReq.getBody())).session(RequestUtils.buildSessionParams(request)).build();
			Object value = scriptParse.runScript(apiInfo.getScript(), apiInfo, apiParams);
			runApiRes.setData(value);
			return ApiResult.success(runApiRes);
		} catch (Throwable e) {
			e.printStackTrace();
			return ApiResult.fail(e.getMessage(), runApiRes);
		} finally {
			runApiRes.setLogs(apiInfoContent.getLogs());
			apiInfoContent.removeAll();
		}
	}

	private Map<String, Object> buildBody(Object body) {
		Map<String, Object> params = new HashMap<>();
		if (body instanceof Map) {
			params.putAll((Map<? extends String, ?>) body);
		}
		params.put(properties.getBodyRootKey(), body);
		return params;
	}

	private Map<String, String> decodeHeaderValue(Map<String, String> header) throws UnsupportedEncodingException {
		Map<String, String> newHeader = new HashMap<>(header.size());
		for (String key : header.keySet()) {
			newHeader.put(key.toLowerCase(), URLDecoder.decode(header.get(key), "utf-8"));
		}
		return newHeader;
	}

	private Map<String, String> getPathVar(String pattern, String url) {
		Integer beginIndex = url.indexOf("/", 8);
		if (beginIndex == -1) {
			return null;
		}
		Integer endIndex = url.indexOf("?") == -1 ? url.length() : url.indexOf("?");
		String path = url.substring(beginIndex, endIndex);
		AntPathMatcher matcher = new AntPathMatcher();
		if (matcher.match(pattern, path)) {
			return matcher.extractUriTemplateVariables(pattern, path);
		}
		return null;
	}

	private Map<String, Object> getParam(String url) {
		Map<String, Object> result = new HashMap<>();
		MultiValueMap<String, String> urlMvp = UriComponentsBuilder.fromHttpUrl(url).build().getQueryParams();
		urlMvp.forEach((key, value) -> {
			String firstValue = CollectionUtils.isEmpty(value) ? null : value.get(0);
			result.put(key, firstValue);
		});
		return result;
	}

	private ApiExample buildLastApiExample(String apiInfoId) {
		List<ApiExample> result = apiInfoService.listApiExampleScript(apiInfoId, 1, 1);
		if (CollectionUtils.isEmpty(result)) {
			return null;
		}
		ApiExample apiExample = result.get(0);
		try {
			if (!StringUtils.isEmpty(apiExample.getResponseBody())) {
				apiExample.setResponseBody(URLDecoder.decode(apiExample.getResponseBody(), "utf-8"));
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return apiExample;
	}

	/**
	 * 目录查询
	 * 
	 * @return
	 */
	@GetMapping("/directory/list")
	public ApiResult directoryList() {
		return ApiResult.success(apiInfoService.loadDirectoryList().stream()
				.sorted(Comparator.comparing(ApiDirectory::getName).thenComparing(ApiDirectory::getPath))
				.collect(Collectors.toList()));
	}

	/**
	 * 目录保存
	 * 
	 * @param directory
	 * @return
	 */
	@PostMapping("/directory")
	public ApiResult saveDirectory(@RequestBody ApiDirectory directory, HttpServletRequest request) {
		try {
			apiInfoService.saveDirectory(directory);
		} catch (Exception e) {
			return ApiResult.fail(e.getMessage());
		}
		return ApiResult.success(directory.getId());
	}

	/**
	 * 目录删除
	 * 
	 * @param directory
	 * @return
	 */
	@DeleteMapping("/directory")
	public ApiResult removeDirectory(@RequestBody ApiDirectory directory, HttpServletRequest request) {
		try {
			apiInfoService.removeDirectory(directory);
		} catch (Exception e) {
			return ApiResult.fail(e.getMessage());
		}
		return ApiResult.success(null);
	}

	/**
	 * 自动完成，类型获取
	 */
	@GetMapping("/completion-items")
	public ApiResult provideCompletionTypes() throws Exception {
		return ApiResult.success(completionService.provideCompletionTypes());
	}

	/**
	 * 自动完成，方法解析
	 */
	@PostMapping("/completion-clazz")
	public ApiResult provideCompletionItems(@RequestBody ProvideCompletionReq completionReq) {
		try {
			Class clazz = Class.forName(completionReq.getClazz());
			return ApiResult.success(completionService.buildMethod(clazz));
		} catch (Throwable e) {
		}
		return ApiResult.success(Collections.emptyList());
	}
}
